/*
 * Copyright © 2017 eqxiu.com 北京中网易企秀科技有限公司  All rights reserved.
 */
package cn.hermit.data.xmljson.handler;

import cn.hermit.data.json.model.JSONValue;
import cn.hermit.data.xml.model.XmlElement;
import cn.hermit.data.xml.model.XmlNameNode;
import cn.hermit.data.json.JSONChar;
import cn.hermit.data.json.model.JSONDocument;
import cn.hermit.data.json.model.JSONObject;
import cn.hermit.util.StringUtils;
import cn.hermit.data.xml.XmlChar;
import cn.hermit.data.xml.model.XmlFactory;
import cn.hermit.data.xml.ns.NamespaceContext;
import cn.hermit.data.xmljson.Badgerfish;

import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

/**
 * Content handler for conversion from JSON to XML with Badgerfish convention.
 * 
 * <p>
 * As there is an overkill and lost for white space in text node, the handler provides a loosen 
 * Badgerfish convention against the official Badgerfish. We called them loosen Badgerfish and strict Badgerfish.
 * </p>
 *  
 * @author Jack Gao (Chinese name : GAO JIANGUO, Email : linux.gjg@gmail.com)
 * @date 3 Jul, 2014
 */
public class J2XBFHandler extends J2XHandler {

    /**
     * Indicate if use strict Badgerfish style
     */
    private boolean strictBadgerfish = true;

    /**
     * For strict Badgerfish style, indicate if use lightweight name space definition in JSON object. 
     */
    private boolean lightWeightXmlns;

    /**
     * Constructor of Content handler for conversion from JSON to XML. Initialized an ordered XML factory.
     */
    public J2XBFHandler() {
        this(null);
    }
    /**
     * Constructor of Content handler for conversion from JSON to XML. 
     * 
     * @param xmlFactory
     * 		XML Factory to create XML objects. If it is null, will initialize an ordered XML factory.
     */
    public J2XBFHandler(XmlFactory xmlFactory) {
        super(xmlFactory);
    }

    /**
     * Load name spaces defined in JSON object to name space context.
     * 
     * @param jo
     * 		The JSON object
     * @param nsContext
     * 		The name space context for generating XML document
     */
    @Override
    protected int loadNamespaces(JSONObject jo, NamespaceContext nsContext) {
        if (strictBadgerfish) {
            Iterator<Entry<String, JSONDocument>> iter = jo.entryIterator();
            while (iter.hasNext()) {
                Entry<String, JSONDocument> entry = iter.next();
                if (isNamespaceKey(entry.getKey())) {
                    boolean hasIncremented = false;
                    if (JSONObject.class.isInstance(entry.getValue())) {
                        JSONObject subJO = (JSONObject) entry.getValue();
                        Iterator<Entry<String, JSONDocument>> nsIter = subJO.entryIterator();
                        while (nsIter.hasNext()) {
                            Entry<String, JSONDocument> e = nsIter.next();
                            if (JSONValue.class.isInstance(e.getValue())) {
                                JSONValue jv = (JSONValue) e.getValue();
                                String nsKey = getNamespaceKey(e.getKey());
                                String ns =
                                        XmlChar.validateText(
                                            jv.getValue().toString(),
                                            this.xmlFactory.getValidator().getDefaultValidator().isReplaceInvalid());
                                ns = JSONChar.unescape(ns, true);
                                ns = ns.replaceAll("\\\\+/", "/");
                                String prefix = XmlNameNode.getPrefix(nsKey);
                                if (this.lightWeightXmlns) {
                                    nsContext.loadNamespace(nsKey, ns, !hasIncremented);
                                    hasIncremented = true;
                                } else if (!nsContext.hasDefinedNS(prefix, ns)) {
                                    nsContext.loadNamespace(nsKey, ns, !hasIncremented);
                                    hasIncremented = true;
                                }
                            }

                        }
                    }
                    return hasIncremented ? nsContext.getCurrentLayer() : -1;
                }
            }
        } else {
            return super.loadNamespaces(jo, nsContext);
        }
        return -1;
    }

    /**
     * Handle name space defined in JSON object for XML Element
     * 
     * @param key
     * 		The key of JSON object in parent JSON Object
     * @param jdoc
     * 		The JSON object
     * @param elem
     * 		The XML element generated by previous recursive callback
     * @param nsLayer
     * 		The name space layer in generated XML document tree
     * @param nsContext
     * 		The name space context for generating XML document
     */
    @Override
    protected void handleNamespace(
            String key,
            JSONDocument jdoc,
            XmlElement elem,
            int nsLayer,
            NamespaceContext nsContext) {
        if (strictBadgerfish) {
            if (JSONObject.class.isInstance(jdoc)) {
                JSONObject jo = (JSONObject) jdoc;
                Iterator<Entry<String, JSONDocument>> nsIter = jo.entryIterator();
                while (nsIter.hasNext()) {
                    Entry<String, JSONDocument> e = nsIter.next();
                    if (JSONValue.class.isInstance(e.getValue())) {
                        JSONValue jv = (JSONValue) e.getValue();
                        String nsKey = getNamespaceKey(e.getKey());
                        String ns =
                                XmlChar.validateText(
                                    jv.getValue().toString(),
                                    this.xmlFactory.getValidator().getDefaultValidator().isReplaceInvalid());
                        ns = JSONChar.unescape(ns, true);
                        ns = ns.replaceAll("\\\\+/", "/");
                        if (this.lightWeightXmlns) {
                            QName name = nsContext.getQName(nsKey);
                            this.xmlFactory.attribute(elem, name, ns);
                        } else {
                            if (nsLayer != -1) {
                                Map<String, String> nsMap = nsContext.getNamespaces(nsLayer);
                                String prefix = XmlNameNode.getPrefix(nsKey);
                                if (nsMap.containsKey(prefix)) {
                                    QName name = nsContext.getQName(nsKey);
                                    this.xmlFactory.attribute(elem, name, ns);
                                }
                            }
                        }
                    }
                }
            }
        } else {
            super.handleNamespace(key, jdoc, elem, nsLayer, nsContext);
        }
    }

    @Override
    protected boolean isElementTag(String key) {
        return !isAttributeKey(key) && !isTextKey(key);
    }

    @Override
    protected String getElementTag(String key) {
        if (strictBadgerfish) {
            return key;
        } else {
            int index = key.indexOf(Badgerfish.KEY_PREFIX_ORDER);
            if (index == -1) {
                return key;
            } else {
                return key.substring(0, index);
            }
        }
    }

    @Override
    protected boolean isNamespaceKey(String key) {
        if (StringUtils.isEmpty(key))
            return false;
        if (strictBadgerfish) {
            return (Badgerfish.KEY_PREFIX_ATTRIBUTE + XMLConstants.XMLNS_ATTRIBUTE).equals(key);
        } else {
            return key.startsWith(Badgerfish.KEY_PREFIX_ATTRIBUTE + XMLConstants.XMLNS_ATTRIBUTE);
        }
    }

    @Override
    protected String getNamespaceKey(String key) {
        if (StringUtils.isEmpty(key)) {
            return XMLConstants.XMLNS_ATTRIBUTE;
        }
        if (strictBadgerfish) {
            if (Badgerfish.KEY_TEXT.equals(key)) {
                return XMLConstants.XMLNS_ATTRIBUTE;
            }
            return XMLConstants.XMLNS_ATTRIBUTE + ':' + key;
        } else {
            int index = key.indexOf(Badgerfish.KEY_PREFIX_ORDER);
            if (index == -1) {
                return key.substring(Badgerfish.KEY_PREFIX_ATTRIBUTE.length());
            } else {
                return key.substring(Badgerfish.KEY_PREFIX_ATTRIBUTE.length(), index);
            }
        }
    }

    @Override
    protected boolean isAttributeKey(String key) {
        return key.startsWith(Badgerfish.KEY_PREFIX_ATTRIBUTE);
    }

    @Override
    protected String getAttributeKey(String key) {
        if (strictBadgerfish) {
            return key.substring(Badgerfish.KEY_PREFIX_ATTRIBUTE.length());
        } else {
            int index = key.indexOf(Badgerfish.KEY_PREFIX_ORDER);
            if (index == -1) {
                return key.substring(Badgerfish.KEY_PREFIX_ATTRIBUTE.length());
            } else {
                return key.substring(Badgerfish.KEY_PREFIX_ATTRIBUTE.length(), index);
            }
        }
    }

    @Override
    protected boolean isTextKey(String key) {
        if (strictBadgerfish) {
            return key.equals(Badgerfish.KEY_TEXT);
        } else {
            return key.startsWith(Badgerfish.KEY_TEXT);
        }
    }

    @Override
    protected boolean isCDataKey(String key) {
        if (strictBadgerfish) {
            return false;
        } else {
            return key.startsWith(Badgerfish.KEY_CDATA);
        }
    }

    /**
     * @return the strictBadgerfish
     */
    public boolean isStrictBadgerfish() {
        return strictBadgerfish;
    }

    /**
     * @param strictBadgerfish
     *            the strictBadgerfish to setHeader
     */
    public void setStrictBadgerfish(boolean strictBadgerfish) {
        this.strictBadgerfish = strictBadgerfish;
    }

    /**
     * @return the lightWeightXmlns
     */
    public boolean isLightWeightXmlns() {
        return lightWeightXmlns;
    }

    /**
     * @param lightWeightXmlns
     *            the lightWeightXmlns to setHeader
     */
    public void setLightWeightXmlns(boolean lightWeightXmlns) {
        lightWeightXmlns = lightWeightXmlns & this.strictBadgerfish;
        this.lightWeightXmlns = lightWeightXmlns;
    }

}